package com.three.mahjong.base.service;

import com.fasterxml.jackson.core.type.TypeReference;
import com.three.message.hall.*;
import com.three.mahjong.base.model.*;
import com.three.cache.redis.lock.RedisLock;
import com.three.cache.redis.manager.RedisManager;
import com.three.common.message.base.ErrorMessage;
import com.three.config.LanguageConfig;
import com.three.config.MJCreateRoomRuleConfig;
import com.three.constant.LanguageId;
import com.three.constant.RedisKeyConstants;
import com.three.domain.account.AccountPo;
import com.three.domain.player.PlayerPo;
import com.three.exception.GameException;
import com.three.repository.AccountRepository;
import com.three.repository.PlayerRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.*;

/**
 * 麻将好友房
 * Created by Mathua on 2017/7/19.
 */
@Component
public class MJFriendRoomManager {

    @Autowired
    private PlayerRepository playerRepository;
    @Autowired
    private AccountRepository accountRepository;

    private String getFriendRoomKey(MJGameType gameType) {
        return gameType.name() + "_" + RoomType.FRIEND_ROOM.name();
    }

    private String getFriendRoomIdKey(MJGameType gameType) {
        return gameType.name() + "_" + RoomType.FRIEND_ROOM.name() + "_id_key" ;
    }

    public void createFriendRoom(CreateFriendRoomReqMessage message) throws GameException {
        // 数据检查
        if(message.getGameType() == null) {
            ErrorMessage.from(message).setCode((short) LanguageId.PARAM_VALUE_ERROR).addData(String.valueOf(message.gameType)).send();
            return;
        }
        if(message.getPayType() == null) {
            ErrorMessage.from(message).setCode((short) LanguageId.PARAM_VALUE_ERROR).addData(String.valueOf(message.payType)).send();
            return;
        }
        if(!MJCreateRoomRuleConfig.checkPayType(message.getGameType(), message.getPayType())) {
            throw new GameException("PayType Value Error:" + message.getPayType());
        }
        if(!MJCreateRoomRuleConfig.checkGameInning(message.getGameType(), message.getMaxRounds())) {
            throw new GameException("GameInning Value Error:" + message.getMaxRounds());
        }
        if(!MJCreateRoomRuleConfig.checkBaseScore(message.getGameType(), message.getBaseScore())) {
            throw new GameException("BaseScore Value Error:" + message.getBaseScore());
        }
        if(!MJCreateRoomRuleConfig.checkCeilingScore(message.getGameType(), message.getCeilingScore())) {
            throw new GameException("CeilingScore Value Error:" + message.getCeilingScore());
        }

        // 是否已在游戏中
        PlayerPo playerPo = playerRepository.getOne(message.getConnection().getSessionContext().getPlayerId());
        // 测试过程周末中的处理
        RedisManager.I.del(RedisKeyConstants.CHESS_HALL_PLAYER_INFO + playerPo.getPlayerId());
        if(RedisManager.I.exists(RedisKeyConstants.CHESS_HALL_PLAYER_INFO + playerPo.getPlayerId())){
            throw new GameException(LanguageConfig.getText(LanguageId.YOU_ARE_IN_GAME));
        }

        RedisLock lock = new RedisLock(getFriendRoomKey(message.getGameType()));
        try {
            // 分布式锁
            if(lock.lock()) {

                // id保持在100000-999999
                long roomId = RedisManager.I.incr(getFriendRoomIdKey(message.getGameType()), 100000L);
                if (roomId > 999999L || roomId < 100000L) {
                    RedisManager.I.del(getFriendRoomIdKey(message.getGameType()));
                    roomId = RedisManager.I.incr(getFriendRoomIdKey(message.getGameType()), 100000L);
                }

                // 构造游戏数据
                List<AccountPo> accountPoList = accountRepository.findByOpenId(message.getConnection().getSessionContext().getOpenId());
                MJTable mjTable = MJTable.build(roomId, message.getGameType(), RoomType.FRIEND_ROOM, message.getPayType(), message.getMaxRounds(), message.getBaseScore(), message.getCeilingScore(), message.getPlayRules());

                // 房主加入
                MJPlayer mjPlayer = MJPlayer.build(roomId, playerPo.getPlayerId(), playerPo.getNickName(), playerPo.getHead(), playerPo.getGender(), accountPoList.get(0).getLoginIp());
                mjTable.memberJoin(mjPlayer);
                RoomInfoForPlayer roomInfoForPlayer = RoomInfoForPlayer.build(message.getGameType(), RoomType.FRIEND_ROOM, roomId);
                RedisManager.I.set(RedisKeyConstants.CHESS_HALL_PLAYER_INFO + playerPo.getPlayerId(), roomInfoForPlayer);

                // 获取缓存数据
                Map<Long, MJTable> map = RedisManager.I.get(getFriendRoomKey(message.getGameType()), new TypeReference<Map<Integer, MJTable>>() {
                }.getType());
                if (map == null)
                    map = new HashMap<>();

                // 将游戏数据存进缓存
                MJTable old = map.putIfAbsent(mjTable.getRoomId(), mjTable);
                if (old != null) {
                    throw new GameException(LanguageConfig.getText(LanguageId.FAIL_TO_CREATE_ROOM_BY_SYSTEM_ERROR));
                }
                RedisManager.I.set(getFriendRoomKey(message.getGameType()), map);

                // 构造响应信息
                NotifyRoomInfoMessage.from(message)
                        .setRoomId((int)mjTable.getRoomId())//
                        .setGameType(mjTable.getGameType().id)//
                        .setPayType(mjTable.getPayType().id)//
                        .setMaxRounds(mjTable.getMaxRounds())//
                        .setCurrentMemberCount(mjTable.getCurrentMemberCount())//
                        .setCreateTime(mjTable.getCreateTime())//
                        .setPlayRules(mjTable.getPlayTypeList())//
                        .setBaseScore(mjTable.getBaseScore())//
                        .setCeilingScore(mjTable.getCeilingScore())//
                        .setRoomOwnerId(mjTable.getRoomOwnerId())//
                        .addPlayers(mjTable.getPlayers().values())
                        .send();
            }
        }
        finally {
            lock.unlock();
        }
    }

    public void joinRoom(JoinRoomReqMessage message) throws GameException {
        RedisLock lock = new RedisLock(message.getGameType().name() + message.getRoomId());
        try {
            // 分布式锁
            if(lock.lock()) {

                // 数据检查
                if (message.getGameType() == null) {
                    ErrorMessage.from(message).setCode((short) LanguageId.PARAM_VALUE_ERROR).addData(String.valueOf(message.gameType)).send();
                    return;
                }

                // 检查是否已加入房间
                PlayerPo playerPo = playerRepository.getOne(message.getConnection().getSessionContext().getPlayerId());
                if (RedisManager.I.exists(RedisKeyConstants.CHESS_HALL_PLAYER_INFO + playerPo.getPlayerId())) {
                    throw new GameException(LanguageConfig.getText(LanguageId.YOU_ARE_IN_GAME));
                }

                // 判断游戏是否存在
                Map<Long, MJTable> map = RedisManager.I.get(getFriendRoomKey(message.getGameType()), new TypeReference<Map<Integer, MJTable>>() {
                }.getType());
                MJTable mjTable = map.get(message.getRoomId());
                if (map == null || mjTable == null) {
                    throw new GameException(LanguageConfig.getText(LanguageId.THE_ROOM_NOT_EXIST));
                }

                // 加入游戏
                List<AccountPo> accountPoList = accountRepository.findByOpenId(message.getConnection().getSessionContext().getOpenId());
                MJPlayer mjPlayer = MJPlayer.build(mjTable.getRoomId(), playerPo.getPlayerId(), playerPo.getNickName(), playerPo.getHead(), playerPo.getGender(), accountPoList.get(0).getLoginIp());
                mjTable.memberJoin(mjPlayer);
                RedisManager.I.set(getFriendRoomKey(message.getGameType()), map);
                RoomInfoForPlayer roomInfoForPlayer = RoomInfoForPlayer.build(message.getGameType(), RoomType.FRIEND_ROOM, mjTable.getRoomId());
                RedisManager.I.set(RedisKeyConstants.CHESS_HALL_PLAYER_INFO + playerPo.getPlayerId(), roomInfoForPlayer);

                // 构造响应信息
                NotifyRoomInfoMessage.from(message)
                        .setRoomId((int) mjTable.getRoomId())//
                        .setGameType(mjTable.getGameType().id)//
                        .setPayType(mjTable.getPayType().id)//
                        .setMaxRounds(mjTable.getMaxRounds())//
                        .setCurrentMemberCount(mjTable.getCurrentMemberCount())//
                        .setCreateTime(mjTable.getCreateTime())//
                        .setPlayRules(mjTable.getPlayTypeList())//
                        .setBaseScore(mjTable.getBaseScore())//
                        .setCeilingScore(mjTable.getCeilingScore())//
                        .setRoomOwnerId(mjTable.getRoomOwnerId())//
                        .addPlayers(mjTable.getPlayers().values())//
                        .send();
            }
        }
        finally {
            lock.unlock();
        }
    }

    public void leaveRoom(LeaveRoomReqMessage message) throws GameException {
        // 判断是否进了房间
        long playId = message.getConnection().getSessionContext().getPlayerId();
        RoomInfoForPlayer roomInfoForPlayer = RedisManager.I.get(RedisKeyConstants.CHESS_HALL_PLAYER_INFO + playId, RoomInfoForPlayer.class);
        if(roomInfoForPlayer == null) {
            throw new GameException(LanguageConfig.getText(LanguageId.YOU_NOT_JOIN_ANOTHER_ROOM));
        }

        RedisLock lock = new RedisLock(roomInfoForPlayer.getGameType().name() + roomInfoForPlayer.getRoomId());
        try {
            // 分布式锁
            if(lock.lock()) {

                // 判断游戏是否存在
                Map<Long, MJTable> map = RedisManager.I.get(getFriendRoomKey(roomInfoForPlayer.getGameType()), new TypeReference<Map<Long, MJTable>>() {
                }.getType());
                MJTable mjTable = null;
                if (map != null)
                    mjTable = map.get(roomInfoForPlayer.getRoomId());
                if (map == null || mjTable == null) {
                    // 删除冗余信息
                    RedisManager.I.del(RedisKeyConstants.CHESS_HALL_PLAYER_INFO + playId);
                    throw new GameException(LanguageConfig.getText(LanguageId.THE_ROOM_NOT_EXIST));
                }

                // 成员离开，更新缓存
                mjTable.memberLeave(playId);
                RedisManager.I.set(getFriendRoomKey(mjTable.getGameType()), map);
                RedisManager.I.del(RedisKeyConstants.CHESS_HALL_PLAYER_INFO + playId);
            }
        }
        finally {
            lock.unlock();
        }
    }

    public void dissolvedRoom(DissolvedRoomReqMessage message) throws GameException {
        // 判断是否进了房间
        long playId = message.getConnection().getSessionContext().getPlayerId();
        RoomInfoForPlayer roomInfoForPlayer = RedisManager.I.get(RedisKeyConstants.CHESS_HALL_PLAYER_INFO + playId, RoomInfoForPlayer.class);
        if(roomInfoForPlayer == null) {
            throw new GameException(LanguageConfig.getText(LanguageId.YOU_NOT_JOIN_ANOTHER_ROOM));
        }

        RedisLock lock = new RedisLock(roomInfoForPlayer.getGameType().name() + roomInfoForPlayer.getRoomId());
        try {
            // 分布式锁
            if(lock.lock()) {

                // 判断游戏是否存在
                Map<Long, MJTable> map = RedisManager.I.get(getFriendRoomKey(roomInfoForPlayer.getGameType()), new TypeReference<Map<Long, MJTable>>() {
                }.getType());
                MJTable mjTable = null;
                if (map != null)
                    mjTable = map.get(roomInfoForPlayer.getRoomId());
                if (map == null || mjTable == null) {
                    // 删除冗余信息
                    RedisManager.I.del(RedisKeyConstants.CHESS_HALL_PLAYER_INFO + playId);
                    throw new GameException(LanguageConfig.getText(LanguageId.THE_ROOM_NOT_EXIST));
                }

                // 解散房间
                mjTable.dissolvedRoom(playId);
                map.remove(roomInfoForPlayer.getRoomId());
                RedisManager.I.set(getFriendRoomKey(mjTable.getGameType()), map);
            }
        }
        finally {
            lock.unlock();
        }

    }

    public void voteToDissolvedRoom(VoteToDissolvedRoomReqMessage message) throws GameException {
        // 判断是否进了房间
        long playId = message.getConnection().getSessionContext().getPlayerId();
        RoomInfoForPlayer roomInfoForPlayer = RedisManager.I.get(RedisKeyConstants.CHESS_HALL_PLAYER_INFO + playId, RoomInfoForPlayer.class);
        if(roomInfoForPlayer == null) {
            throw new GameException(LanguageConfig.getText(LanguageId.YOU_NOT_JOIN_ANOTHER_ROOM));
        }

        RedisLock lock = new RedisLock(roomInfoForPlayer.getGameType().name() + roomInfoForPlayer.getRoomId());
        try {
            // 分布式锁
            if(lock.lock()) {
                // 判断游戏是否存在
                Map<Long, MJTable> map = RedisManager.I.get(getFriendRoomKey(roomInfoForPlayer.getGameType()), new TypeReference<Map<Long, MJTable>>() {
                }.getType());
                MJTable mjTable = null;
                if (map != null)
                    mjTable = map.get(roomInfoForPlayer.getRoomId());
                if (map == null || mjTable == null) {
                    // 删除冗余信息
                    RedisManager.I.del(RedisKeyConstants.CHESS_HALL_PLAYER_INFO + playId);
                    throw new GameException(LanguageConfig.getText(LanguageId.THE_ROOM_NOT_EXIST));
                }

                mjTable.voteToDissolved(playId);
                RedisManager.I.set(getFriendRoomKey(mjTable.getGameType()), map);
            }
        }
        finally {
            lock.unlock();
        }

    }

    public void dealVote(DealVoteReqMessage message) throws GameException {
        // 判断是否进了房间
        long playId = message.getConnection().getSessionContext().getPlayerId();
        RoomInfoForPlayer roomInfoForPlayer = RedisManager.I.get(RedisKeyConstants.CHESS_HALL_PLAYER_INFO + playId, RoomInfoForPlayer.class);
        if(roomInfoForPlayer == null) {
            throw new GameException(LanguageConfig.getText(LanguageId.YOU_NOT_JOIN_ANOTHER_ROOM));
        }

        RedisLock lock = new RedisLock(roomInfoForPlayer.getGameType().name() + roomInfoForPlayer.getRoomId());
        try {
            // 分布式锁
            if(lock.lock()) {

                // 判断游戏是否存在
                Map<Long, MJTable> map = RedisManager.I.get(getFriendRoomKey(roomInfoForPlayer.getGameType()), new TypeReference<Map<Long, MJTable>>() {
                }.getType());
                MJTable mjTable = null;
                if (map != null)
                    mjTable = map.get(roomInfoForPlayer.getRoomId());
                if (map == null || mjTable == null) {
                    // 删除冗余信息
                    RedisManager.I.del(RedisKeyConstants.CHESS_HALL_PLAYER_INFO + playId);
                    throw new GameException(LanguageConfig.getText(LanguageId.THE_ROOM_NOT_EXIST));
                }

                // 处理投票
                mjTable.dealVote(playId, message.isAgree());
                // 根据投票情况决定是否解散
                if (mjTable.isHadVoteDissolved())
                    map.remove(roomInfoForPlayer.getRoomId());
                RedisManager.I.set(getFriendRoomKey(mjTable.getGameType()), map);
            }
        }
        finally {
            lock.unlock();
        }
    }

    public void readyGame(ReadyGameReqMessage message) throws GameException {
        // 判断是否进了房间
        long playId = message.getConnection().getSessionContext().getPlayerId();
        RoomInfoForPlayer roomInfoForPlayer = RedisManager.I.get(RedisKeyConstants.CHESS_HALL_PLAYER_INFO + playId, RoomInfoForPlayer.class);
        if(roomInfoForPlayer == null) {
            throw new GameException(LanguageConfig.getText(LanguageId.YOU_NOT_JOIN_ANOTHER_ROOM));
        }

        RedisLock lock = new RedisLock(roomInfoForPlayer.getGameType().name() + roomInfoForPlayer.getRoomId());
        try {
            // 分布式锁
            if(lock.lock()) {

                // 判断游戏是否存在
                Map<Long, MJTable> map = RedisManager.I.get(getFriendRoomKey(roomInfoForPlayer.getGameType()), new TypeReference<Map<Integer, MJTable>>() {
                }.getType());
                MJTable mjTable = null;
                if (map != null)
                    mjTable = map.get(roomInfoForPlayer.getRoomId());
                if (map == null || mjTable == null) {
                    // 删除冗余信息
                    RedisManager.I.del(RedisKeyConstants.CHESS_HALL_PLAYER_INFO + playId);
                    throw new GameException(LanguageConfig.getText(LanguageId.THE_ROOM_NOT_EXIST));
                }

                mjTable.readyGame(playId);
                ReadyGameRespMessage.from(message).send();
                RedisManager.I.set(getFriendRoomKey(mjTable.getGameType()), map);
            }
        }
        finally {
            lock.unlock();
        }
    }

    /**
     * 通过玩家id找到table
     * @param playerId
     * @return
     */
    public MJTable getTableByPlayerId(long playerId) {
        RoomInfoForPlayer roomInfoForPlayer = RedisManager.I.get(RedisKeyConstants.CHESS_HALL_PLAYER_INFO + playerId, RoomInfoForPlayer.class);
        if(roomInfoForPlayer == null) {
            throw  new GameException(LanguageConfig.getText(LanguageId.YOU_NOT_JOIN_ANOTHER_ROOM));
        }

        RedisLock lock = new RedisLock(roomInfoForPlayer.getGameType().name() + roomInfoForPlayer.getRoomId());

        try {
            if(lock.lock()) {
                Map<Long, MJTable> map = RedisManager.I.get(getFriendRoomKey(roomInfoForPlayer.getGameType()), new TypeReference<Map<Integer, MJTable>>() {
                }.getType());
                MJTable mjTable = null;
                if (map != null)
                    mjTable = map.get(roomInfoForPlayer.getRoomId());
                if (map == null || mjTable == null) {
                    // 删除冗余信息
                    RedisManager.I.del(RedisKeyConstants.CHESS_HALL_PLAYER_INFO + playerId);
                    throw new GameException(LanguageConfig.getText(LanguageId.THE_ROOM_NOT_EXIST));
                }
                return mjTable;
            }
        }
        finally {
            lock.unlock();
            return null;
        }
    }
}
